home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / DefaultStyledDocument.java < prev    next >
Text File  |  1998-06-30  |  45KB  |  1,494 lines

  1. /*
  2.  * @(#)DefaultStyledDocument.java    1.73 98/04/09
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text;
  21.  
  22. import java.awt.Color;
  23. import java.awt.Component;
  24. import java.awt.Font;
  25. import java.awt.FontMetrics;
  26. import java.util.Hashtable;
  27. import java.util.Stack;
  28. import java.util.Vector;
  29. import java.io.Serializable;
  30. import com.sun.java.swing.Icon;
  31. import com.sun.java.swing.event.*;
  32. import com.sun.java.swing.undo.AbstractUndoableEdit;
  33. import com.sun.java.swing.undo.CannotRedoException;
  34. import com.sun.java.swing.undo.CannotUndoException;
  35.  
  36. /**
  37.  * A document that can be marked up with character and paragraph 
  38.  * styles in a manner similar to the Rich Text Format.  The element
  39.  * structure for this document represents style crossings for
  40.  * style runs.  These style runs are mapped into a paragraph element 
  41.  * structure (which may reside in some other structure).  The 
  42.  * style runs break at paragraph boundries since logical styles are 
  43.  * assigned to paragraph boundries.
  44.  * <p>
  45.  * Warning: serialized objects of this class will not be compatible with
  46.  * future swing releases.  The current serialization support is appropriate
  47.  * for short term storage or RMI between Swing1.0 applications.  It will
  48.  * not be possible to load serialized Swing1.0 objects with future releases
  49.  * of Swing.  The JDK1.2 release of Swing will be the compatibility
  50.  * baseline for the serialized form of Swing objects.
  51.  *
  52.  * @author  Timothy Prinzing
  53.  * @version 1.73 04/09/98
  54.  * @see     Document
  55.  * @see     AbstractDocument
  56.  */
  57. public class DefaultStyledDocument extends AbstractDocument implements StyledDocument {
  58.  
  59.     /**
  60.      * Constructs a styled document.
  61.      *
  62.      * @param c  the container for the content
  63.      * @param styles resources and style definitions which may
  64.      *  be shared across documents
  65.      */
  66.     public DefaultStyledDocument(Content c, StyleContext styles) {
  67.     super(c, styles);
  68.     buffer = new ElementBuffer(createDefaultRoot());
  69.     Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
  70.     setLogicalStyle(0, defaultStyle);
  71.     }
  72.  
  73.     /**
  74.      * Constructs a styled document with the default content
  75.      * storage implementation and a shared set of styles.
  76.      *
  77.      * @param styles the styles
  78.      */
  79.     public DefaultStyledDocument(StyleContext styles) {
  80.     this(new StringContent(BUFFER_SIZE_DEFAULT), styles);
  81.     }
  82.  
  83.     /**
  84.      * Constructs a default styled document.  This buffers
  85.      * input content by a size of <em>BUFFER_SIZE_DEFAULT</em> 
  86.      * and has a style context that is scoped by the lifetime
  87.      * of the document and is not shared with other documents.
  88.      */
  89.     public DefaultStyledDocument() {
  90.     this(new StringContent(BUFFER_SIZE_DEFAULT), new StyleContext());
  91.     }
  92.  
  93.     /**
  94.      * Gets the default root element.
  95.      *
  96.      * @return the root
  97.      * @see Document#getDefaultRootElement
  98.      */
  99.     public Element getDefaultRootElement() {
  100.     return buffer.getRootElement();
  101.     }
  102.  
  103.     /**
  104.      * Inserts new elements in bulk.  This is useful to allow
  105.      * parsing with the document in an unlocked state and
  106.      * prepare an element structure modification.  This method
  107.      * takes an array of tokens that describe how to update an
  108.      * element structure so the time within a write lock can
  109.      * be greatly reduced in an asynchronous update situation.     
  110.      * <p>
  111.      * This method is thread safe, although most Swing methods
  112.      * are not. Please see 
  113.      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  114.      * and Swing</A> for more information.     
  115.      *
  116.      * @param offset the starting offset >= 0
  117.      * @param data the element data
  118.      * @exception BadLocationException for an invalid starting offset
  119.      */
  120.     protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
  121.     try {
  122.         writeLock();
  123.  
  124.         // install the content
  125.         Content c = getContent();
  126.         int n = data.length;
  127.         int pos = offset;
  128.         for (int i = 0; i < n; i++) {
  129.         ElementSpec es = data[i];
  130.         if (es.getLength() > 0) {
  131.             c.insertString(pos, new String(es.getArray(), es.getOffset(), 
  132.                            es.getLength()));
  133.             pos += es.getLength();
  134.         }
  135.         }
  136.  
  137.         // build the element structure
  138.         int length = pos - offset;
  139.         DefaultDocumentEvent evnt = 
  140.           new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  141.         buffer.insert(offset, length, data, evnt);
  142.         evnt.end();
  143.         fireInsertUpdate(evnt);
  144.         fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
  145.     } finally {
  146.         writeUnlock();
  147.     }
  148.     }
  149.  
  150.     /**
  151.      * Adds a new style into the logical style hierarchy.  Style attributes
  152.      * resolve from bottom up so an attribute specified in a child
  153.      * will override an attribute specified in the parent.
  154.      *
  155.      * @param nm   the name of the style (must be unique within the
  156.      *   collection of named styles).  The name may be null if the style 
  157.      *   is unnamed, but the caller is responsible
  158.      *   for managing the reference returned as an unnamed style can't
  159.      *   be fetched by name.  An unnamed style may be useful for things
  160.      *   like character attribute overrides such as found in a style 
  161.      *   run.
  162.      * @param parent the parent style.  This may be null if unspecified
  163.      *   attributes need not be resolved in some other style.
  164.      * @return the style
  165.      */
  166.     public Style addStyle(String nm, Style parent) {
  167.     StyleContext styles = (StyleContext) getAttributeContext();
  168.     return styles.addStyle(nm, parent);
  169.     }
  170.  
  171.     /**
  172.      * Removes a named style previously added to the document.  
  173.      *
  174.      * @param nm  the name of the style to remove
  175.      */
  176.     public void removeStyle(String nm) {
  177.     StyleContext styles = (StyleContext) getAttributeContext();
  178.     styles.removeStyle(nm);
  179.     }
  180.  
  181.     /**
  182.      * Fetches a named style previously added.
  183.      *
  184.      * @param nm  the name of the style
  185.      * @return the style
  186.      */
  187.     public Style getStyle(String nm) {
  188.     StyleContext styles = (StyleContext) getAttributeContext();
  189.     return styles.getStyle(nm);
  190.     }
  191.  
  192.     /**
  193.      * Sets the logical style to use for the paragraph at the
  194.      * given position.  If attributes aren't explicitly set 
  195.      * for character and paragraph attributes they will resolve 
  196.      * through the logical style assigned to the paragraph, which
  197.      * in turn may resolve through some hierarchy completely 
  198.      * independent of the element hierarchy in the document.
  199.      * <p>
  200.      * This method is thread safe, although most Swing methods
  201.      * are not. Please see 
  202.      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  203.      * and Swing</A> for more information.     
  204.      *
  205.      * @param pos the offset from the start of the document >= 0
  206.      * @param s  the logical style to assign to the paragraph, null if none
  207.      */
  208.     public void setLogicalStyle(int pos, Style s) {
  209.     Element paragraph = getParagraphElement(pos);
  210.     if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
  211.         try {
  212.         writeLock();
  213.         StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
  214.         ((AbstractElement)paragraph).setResolveParent(s);
  215.         int p0 = paragraph.getStartOffset();
  216.         int p1 = paragraph.getEndOffset();
  217.         DefaultDocumentEvent e = 
  218.           new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
  219.         e.addEdit(edit);
  220.         e.end();
  221.         fireChangedUpdate(e);
  222.         fireUndoableEditUpdate(new UndoableEditEvent(this, e));
  223.         } finally {
  224.         writeUnlock();
  225.         }
  226.     }
  227.     }
  228.  
  229.     /** 
  230.      * Fetches the logical style assigned to the paragraph 
  231.      * represented by the given position.
  232.      *
  233.      * @param p the location to translate to a paragraph
  234.      *  and determine the logical style assigned >= 0.  This
  235.      *  is an offset from the start of the document.
  236.      * @return the style, null if none
  237.      */
  238.     public Style getLogicalStyle(int p) {
  239.     Style s = null;
  240.     Element paragraph = getParagraphElement(p);
  241.     if (paragraph != null) {
  242.         AttributeSet a = paragraph.getAttributes();
  243.         s = (Style) a.getResolveParent();
  244.     }
  245.     return s;
  246.     }
  247.  
  248.     /**
  249.      * Sets attributes for some part of the document.
  250.      * A write lock is held by this operation while changes
  251.      * are being made, and a DocumentEvent is sent to the listeners 
  252.      * after the change has been successfully completed.
  253.      * <p>
  254.      * This method is thread safe, although most Swing methods
  255.      * are not. Please see 
  256.      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  257.      * and Swing</A> for more information.     
  258.      *
  259.      * @param offset the offset in the document >= 0
  260.      * @param length the length >= 0
  261.      * @param s the attributes
  262.      * @param replace true if the previous attributes should be replaced
  263.      *  before setting the new attributes
  264.      */
  265.     public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace) {
  266.     try {
  267.         writeLock();
  268.         DefaultDocumentEvent changes = 
  269.         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  270.  
  271.         // split elements that need it
  272.         buffer.change(offset, length, changes);
  273.  
  274.         AttributeSet sCopy = s.copyAttributes();
  275.  
  276.         // PENDING(prinz) - this isn't a very efficient way to iterate
  277.         int lastEnd = Integer.MAX_VALUE;
  278.         for (int pos = offset; pos < (offset + length); pos = lastEnd) {
  279.         Element run = getCharacterElement(pos);
  280.         lastEnd = run.getEndOffset();
  281.         MutableAttributeSet attr = (MutableAttributeSet) run.getAttributes();
  282.         changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
  283.         if (replace) {
  284.             attr.removeAttributes(attr);
  285.         }
  286.         attr.addAttributes(s);
  287.         }
  288.         changes.end();
  289.         fireChangedUpdate(changes);
  290.         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  291.     } finally {
  292.         writeUnlock();
  293.     }
  294.  
  295.     }
  296.  
  297.     /**
  298.      * Sets attributes for a paragraph.
  299.      * <p>
  300.      * This method is thread safe, although most Swing methods
  301.      * are not. Please see 
  302.      * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  303.      * and Swing</A> for more information.     
  304.      *
  305.      * @param offset the offset into the paragraph >= 0
  306.      * @param length the number of characters affected >= 0
  307.      * @param s the attributes
  308.      * @param replace whether to replace existing attributes, or merge them
  309.      */
  310.     public void setParagraphAttributes(int offset, int length, AttributeSet s, 
  311.                        boolean replace) {
  312.     try {
  313.         writeLock();
  314.         DefaultDocumentEvent changes = 
  315.         new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
  316.  
  317.         AttributeSet sCopy = s.copyAttributes();
  318.  
  319.         // PENDING(prinz) - this assumes a particular element structure
  320.         Element section = getDefaultRootElement();
  321.         int index0 = section.getElementIndex(offset);
  322.         int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
  323.         for (int i = index0; i <= index1; i++) {
  324.         Element paragraph = section.getElement(i);
  325.         MutableAttributeSet attr = (MutableAttributeSet) paragraph.getAttributes();
  326.         changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
  327.         if (replace) {
  328.             attr.removeAttributes(attr);
  329.         }
  330.         attr.addAttributes(s);
  331.         }
  332.         changes.end();
  333.         fireChangedUpdate(changes);
  334.         fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  335.     } finally {
  336.         writeUnlock();
  337.     }
  338.     }
  339.  
  340.     /**
  341.      * Gets a paragraph element.
  342.      *
  343.      * @param pos the starting offset >= 0
  344.      * @return the element
  345.      */
  346.     public Element getParagraphElement(int pos) {
  347.     Element section = getDefaultRootElement();
  348.     int index = section.getElementIndex(pos);
  349.     Element paragraph = section.getElement(index);
  350.     return paragraph;
  351.     }
  352.  
  353.     /**
  354.      * Gets a character element based on a position.
  355.      *
  356.      * @param pos the position in the document >= 0
  357.      * @return the element
  358.      */
  359.     public Element getCharacterElement(int pos) {
  360.     Element e = null;
  361.     for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
  362.         int index = e.getElementIndex(pos);
  363.         e = e.getElement(index);
  364.     }
  365.     return e;
  366.     }
  367.  
  368.     // --- local methods -------------------------------------------------
  369.  
  370.     /**
  371.      * Updates document structure as a result of text insertion.  This
  372.      * will happen within a write lock.  This implementation simply
  373.      * parses the inserted content for line breaks and builds up a set
  374.      * of instructions for the element buffer.
  375.      *
  376.      * @param chng a description of the document change
  377.      * @param attr the attributes
  378.      */
  379.     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  380.     int offset = chng.getOffset();
  381.     int length = chng.getLength();
  382.     if (attr == null) {
  383.         attr = SimpleAttributeSet.EMPTY;
  384.     }
  385.  
  386.     Element paragraph = getParagraphElement(offset + length);
  387.     Element run = paragraph.getElement(paragraph.getElementIndex(offset + length));
  388.     AttributeSet pattr = paragraph.getAttributes();
  389.     AttributeSet cattr = run.getAttributes();
  390.  
  391.     try {
  392.         boolean breakAtStart = false;
  393.         boolean breakAtEnd = false;
  394.         Segment s = new Segment();
  395.         Vector parseBuffer = new Vector();
  396.         getText(offset, length, s);
  397.         char[] txt = s.array;
  398.         int n = s.offset + s.count;
  399.         int lastOffset = s.offset;
  400.         for (int i = s.offset; i < n; i++) {
  401.         if (txt[i] == '\n') {
  402.             int breakOffset = i + 1;
  403.             parseBuffer.addElement(
  404.                         new ElementSpec(attr, ElementSpec.ContentType,
  405.                            breakOffset - lastOffset));
  406.             parseBuffer.addElement(
  407.                         new ElementSpec(null, ElementSpec.EndTagType));
  408.             parseBuffer.addElement(
  409.                         new ElementSpec(pattr, ElementSpec.StartTagType));
  410.             lastOffset = breakOffset;
  411.         }
  412.         }
  413.         if (lastOffset < n) {
  414.         parseBuffer.addElement(
  415.                     new ElementSpec(attr, ElementSpec.ContentType,
  416.                        n - lastOffset));
  417.         } else {
  418.         breakAtEnd = true;
  419.         }
  420.         if (offset > 0) {
  421.         getText(offset - 1, 1, s);
  422.         if (s.array[s.offset] == '\n') {
  423.             breakAtStart = true;
  424.             ElementSpec spec = new ElementSpec(pattr, ElementSpec.StartTagType);
  425.             parseBuffer.insertElementAt(spec, 0);
  426.             spec = new ElementSpec(pattr, ElementSpec.EndTagType);
  427.             parseBuffer.insertElementAt(spec, 0);
  428.         }
  429.         }
  430.         ElementSpec first = (ElementSpec) parseBuffer.firstElement();
  431.         if ((breakAtStart == false) && cattr.isEqual(attr) && (offset > 0)) {
  432.         first.setDirection(ElementSpec.JoinPreviousDirection);
  433.         }
  434.         if (((parseBuffer.size() > 1) || 
  435.          (first.getDirection() != ElementSpec.JoinPreviousDirection)) && 
  436.         (breakAtEnd == false)) {
  437.  
  438.         ElementSpec last = (ElementSpec) parseBuffer.lastElement();
  439.         if (run.getEndOffset() <= (offset + length)) {
  440.             cattr = getCharacterElement(offset+length).getAttributes();
  441.         }
  442.         if (cattr.isEqual(attr)) {
  443.             last.setDirection(ElementSpec.JoinNextDirection);
  444.         }
  445.         }
  446.  
  447.         ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
  448.         parseBuffer.copyInto(spec);
  449.         buffer.insert(offset, length, spec, chng);
  450.     } catch (BadLocationException bl) {
  451.     }
  452.     }
  453.  
  454.     /**
  455.      * Updates document structure as a result of text removal.
  456.      *
  457.      * @param chng a description of the document change
  458.      */
  459.     protected void removeUpdate(DefaultDocumentEvent chng) {
  460.     buffer.remove(chng.getOffset(), chng.getLength(), chng);
  461.     }
  462.  
  463.     /**
  464.      * Creates the root element to be used to represent the
  465.      * default document structure.
  466.      *
  467.      * @return the element base
  468.      */
  469.     protected AbstractElement createDefaultRoot() {
  470.     // grabs a write-lock for this initialization and
  471.     // abandon it during initialization so in normal
  472.     // operation we can detect an illegitimate attempt
  473.     // to mutate attributes.
  474.     writeLock();
  475.     BranchElement section = new SectionElement();
  476.     BranchElement paragraph = new BranchElement(section, null);
  477.  
  478.     LeafElement brk = new LeafElement(paragraph, null, 0, 1);
  479.     Element[] buff = new Element[1];
  480.     buff[0] = brk;
  481.     paragraph.replace(0, 0, buff);
  482.  
  483.     buff[0] = paragraph;
  484.     section.replace(0, 0, buff);
  485.     writeUnlock();
  486.     return section;
  487.     }
  488.  
  489.     /**
  490.      * Gets the foreground color from an attribute set.
  491.      *
  492.      * @param attr the attribute set
  493.      * @return the color
  494.      */
  495.     public Color getForeground(AttributeSet attr) {
  496.     return StyleConstants.getForeground(attr);
  497.     }
  498.  
  499.     /**
  500.      * Gets the background color from an attribute set.
  501.      *
  502.      * @param attr the attribute set
  503.      * @return the color
  504.      */
  505.     public Color getBackground(AttributeSet attr) {
  506.     throw new Error("not implemented");
  507.     }
  508.  
  509.     /**
  510.      * Gets the font from an attribute set.
  511.      *
  512.      * @param attr the attribute set
  513.      * @return the font
  514.      */
  515.     public Font getFont(AttributeSet attr) {
  516.     StyleContext styles = (StyleContext) getAttributeContext();
  517.     return styles.getFont(attr);
  518.     }
  519.  
  520.     // --- member variables -----------------------------------------------------------
  521.  
  522.     /**
  523.      * The default size of the initial content buffer.
  524.      */
  525.     public static final int BUFFER_SIZE_DEFAULT = 4096;
  526.  
  527.     private ElementBuffer buffer;
  528.  
  529.     /**
  530.      * Default root element for a document... maps out the 
  531.      * paragraphs/lines contained.
  532.      * <p>
  533.      * Warning: serialized objects of this class will not be compatible with
  534.      * future swing releases.  The current serialization support is appropriate
  535.      * for short term storage or RMI between Swing1.0 applications.  It will
  536.      * not be possible to load serialized Swing1.0 objects with future releases
  537.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  538.      * baseline for the serialized form of Swing objects.
  539.      */
  540.     protected class SectionElement extends BranchElement {
  541.  
  542.         /**
  543.          * Creates a new SectionElement.
  544.          */
  545.     public SectionElement() {
  546.         super(null, null);
  547.     }
  548.  
  549.         /**
  550.          * Gets the name of the element.
  551.          *
  552.          * @return the name
  553.          */
  554.         public String getName() {
  555.         return SectionElementName;
  556.     }
  557.     }
  558.  
  559.     /**
  560.      * Specification for building elements.
  561.      * <p>
  562.      * Warning: serialized objects of this class will not be compatible with      * future swing releases.  The current serialization support is appropriate
  563.      * for short term storage or RMI between Swing1.0 applications.  It will
  564.      * not be possible to load serialized Swing1.0 objects with future releases
  565.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  566.      * baseline for the serialized form of Swing objects.
  567.      */
  568.     public static class ElementSpec {
  569.  
  570.     /**
  571.      * A possible value for getType.  This specifies
  572.      * that this record type is a start tag and
  573.      * represents markup that specifies the start
  574.      * of an element.
  575.      */
  576.     public static final short StartTagType = 1;
  577.     
  578.     /**
  579.      * A possible value for getType.  This specifies
  580.      * that this record type is a end tag and
  581.      * represents markup that specifies the end
  582.      * of an element.
  583.      */
  584.     public static final short EndTagType = 2;
  585.  
  586.     /**
  587.      * A possible value for getType.  This specifies
  588.      * that this record type represents content.
  589.      */
  590.     public static final short ContentType = 3;
  591.     
  592.     /**
  593.      * A possible value for getDirection.  This specifies
  594.      * that the data associated with this record should
  595.      * be joined to what precedes it.
  596.      */
  597.     public static final short JoinPreviousDirection = 4;
  598.     
  599.     /**
  600.      * A possible value for getDirection.  This specifies
  601.      * that the data associated with this record should
  602.      * be joined to what follows it.
  603.      */
  604.     public static final short JoinNextDirection = 5;
  605.     
  606.     /**
  607.      * A possible value for getDirection.  This specifies
  608.      * that the data associated with this record should
  609.      * be used to originate a new element.  This would be
  610.      * the normal value.
  611.      */
  612.     public static final short OriginateDirection = 6;
  613.  
  614.     
  615.     /**
  616.      * Constructor useful for markup when the markup will not
  617.      * be stored in the document.
  618.          *
  619.          * @param a the attributes for the element
  620.          * @param type the type of the element (StartTagType, EndTagType,
  621.          *  ContentType)
  622.      */
  623.     public ElementSpec(AttributeSet a, short type) {
  624.         this(a, type, null, 0, 0);
  625.     }
  626.  
  627.     /**
  628.      * Constructor for parsing inside the document when
  629.      * the data has already been added, but len information
  630.      * is needed.
  631.          *
  632.          * @param a the attributes for the element
  633.          * @param type the type of the element (StartTagType, EndTagType,
  634.          *  ContentType)
  635.          * @param len the length >= 0
  636.      */
  637.     public ElementSpec(AttributeSet a, short type, int len) {
  638.         this(a, type, null, 0, len);
  639.     }
  640.  
  641.     /**
  642.      * Constructor for creating a spec externally for batch
  643.      * input of content and markup into the document.
  644.          *
  645.          * @param a the attributes for the element
  646.          * @param type the type of the element (StartTagType, EndTagType,
  647.          *  ContentType)
  648.          * @param txt the text for the element
  649.          * @param offs the offset into the text >= 0
  650.          * @param len the length of the text >= 0
  651.      */
  652.         public ElementSpec(AttributeSet a, short type, char[] txt, 
  653.                   int offs, int len) {
  654.         attr = a;
  655.         this.type = type;
  656.         this.data = txt;
  657.         this.offs = offs;
  658.         this.len = len;
  659.         this.direction = OriginateDirection;
  660.     }
  661.  
  662.         /**
  663.          * Sets the element type.
  664.          *
  665.          * @param type the type of the element (StartTagType, EndTagType,
  666.          *  ContentType)
  667.          */
  668.     public void setType(short type) {
  669.         this.type = type;
  670.     }
  671.  
  672.         /**
  673.          * Gets the element type.
  674.          *
  675.          * @return  the type of the element (StartTagType, EndTagType,
  676.          *  ContentType)
  677.          */
  678.     public short getType() {
  679.         return type;
  680.     }
  681.  
  682.         /**
  683.          * Sets the direction.
  684.          *
  685.          * @param direction the direction (JoinPreviousDirection,
  686.          *   JoinNextDirection)
  687.          */
  688.     public void setDirection(short direction) {
  689.         this.direction = direction;
  690.     }
  691.  
  692.         /**
  693.          * Gets the direction.
  694.          *
  695.          * @return the direction (JoinPreviousDirection, JoinNextDirection)
  696.          */
  697.     public short getDirection() {
  698.         return direction;
  699.     }
  700.  
  701.         /**
  702.          * Gets the element attributes.
  703.          *
  704.          * @return the attribute set
  705.          */
  706.     public AttributeSet getAttributes() {
  707.         return attr;
  708.     }
  709.  
  710.         /**
  711.          * Gets the array of characters.
  712.          *
  713.          * @return the array
  714.          */
  715.     public char[] getArray() {
  716.         return data;
  717.     }
  718.  
  719.  
  720.         /**
  721.          * Gets the starting offset.
  722.          *
  723.          * @return the offset >= 0
  724.          */
  725.     public int getOffset() {
  726.         return 0;
  727.     }
  728.  
  729.         /**
  730.          * Gets the length.
  731.          *
  732.          * @return the length >= 0
  733.          */
  734.     public int getLength() {
  735.         return len;
  736.     }
  737.  
  738.         /**
  739.          * Converts the element to a string.
  740.          *
  741.          * @return the string
  742.          */
  743.         public String toString() {
  744.         String tlbl = "??";
  745.         String plbl = "??";
  746.         switch(type) {
  747.         case StartTagType:
  748.         tlbl = "StartTag";
  749.         break;
  750.         case ContentType:
  751.         tlbl = "Content";
  752.         break;
  753.         case EndTagType:
  754.         tlbl = "EndTag";
  755.         break;
  756.         }
  757.         switch(direction) {
  758.         case JoinPreviousDirection:
  759.         plbl = "JoinPrevious";
  760.         break;
  761.         case JoinNextDirection:
  762.         plbl = "JoinNext";
  763.         break;
  764.         case OriginateDirection:
  765.         plbl = "Originate";
  766.         break;
  767.         }
  768.         return tlbl + ":" + plbl + ":" + getLength();
  769.     }
  770.         
  771.     private AttributeSet attr;
  772.     private int len;
  773.     private short type;
  774.     private short direction;
  775.  
  776.     private int offs;
  777.     private char[] data;
  778.     }
  779.  
  780.     /**
  781.      * Class to manage changes to the element
  782.      * hierarchy.
  783.      * <p>
  784.      * Warning: serialized objects of this class will not be compatible with
  785.      * future swing releases.  The current serialization support is appropriate
  786.      * for short term storage or RMI between Swing1.0 applications.  It will
  787.      * not be possible to load serialized Swing1.0 objects with future releases
  788.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  789.      * baseline for the serialized form of Swing objects.
  790.      */
  791.     public class ElementBuffer implements Serializable {
  792.  
  793.         /**
  794.          * Creates a new ElementBuffer.
  795.          *
  796.          * @param root the root element
  797.          */
  798.     public ElementBuffer(Element root) {
  799.         this.root = root;
  800.         changes = new Vector();
  801.         path = new Stack();
  802.         endJoin = new Vector();
  803.     }
  804.  
  805.         /**
  806.          * Gets the root element.
  807.          *
  808.          * @return the root element
  809.          */
  810.         public Element getRootElement() {
  811.         return root;
  812.     }
  813.  
  814.         /**
  815.          * Inserts new content.
  816.          *
  817.          * @param offset the starting offset >= 0
  818.          * @param length the length >= 0
  819.          * @param data the data to insert
  820.          * @param de the event capturing this edit
  821.          */
  822.     public final void insert(int offset, int length, ElementSpec[] data,
  823.                  DefaultDocumentEvent de) {
  824.         insertOp = true;
  825.         beginEdits(offset, length);
  826.         insertUpdate(data);
  827.         endEdits(de);
  828.         insertOp = false;
  829.     }
  830.  
  831.         /**
  832.          * Removes content.
  833.          *
  834.          * @param offset the starting offset >= 0
  835.          * @param length the length >= 0
  836.          * @param de the event capturing this edit
  837.          */
  838.     public final void remove(int offset, int length, DefaultDocumentEvent de) {
  839.         beginEdits(offset, length);
  840.         removeUpdate();
  841.         endEdits(de);
  842.     }
  843.  
  844.         /**
  845.          * Changes content.
  846.          *
  847.          * @param offset the starting offset >= 0
  848.          * @param length the length >= 0
  849.          * @param de the event capturing this edit
  850.          */
  851.         public final void change(int offset, int length, DefaultDocumentEvent de) {
  852.         beginEdits(offset, length);
  853.         changeUpdate();
  854.         endEdits(de);
  855.     }
  856.  
  857.         /**
  858.          * Inserts an update into the document.
  859.          *
  860.          * @param data the elements to insert
  861.          */
  862.     protected void insertUpdate(ElementSpec[] data) {
  863.         // push the path
  864.         Element elem = root;
  865.         int index = elem.getElementIndex(offset);
  866.         while (! elem.isLeaf()) {
  867.         Element child = elem.getElement(index);
  868.         push(elem, (child.isLeaf() ? index : index+1));
  869.         elem = child;
  870.         index = elem.getElementIndex(offset);
  871.         }
  872.  
  873.         // open a hole to inject new elements (if needed)
  874.         open(data);
  875.  
  876.         // fold in the specified subtree
  877.         int n = data.length;
  878.         for (int i = 0; i < n; i++) {
  879.         insertElement(data[i]);
  880.         }
  881.  
  882.         // close up the hole in the tree
  883.         close();
  884.  
  885.         // pop the remaining path
  886.         while (path.size() != 0) {
  887.         pop();
  888.         }
  889.     }
  890.  
  891.     /**
  892.      * Updates the element structure in response to a removal from the
  893.      * associated sequence in the document.  Any elements consumed by the
  894.      * span of the removal are removed.  
  895.      */
  896.     protected void removeUpdate() {
  897.         removeElements(root, offset, offset + length);
  898.     }
  899.  
  900.         /**
  901.          * Updates the element structure in response to a change in the
  902.          * document.
  903.          */
  904.         protected void changeUpdate() {
  905.         boolean didEnd = split(offset, length);
  906.         if (! didEnd) {
  907.         // need to do the other end
  908.         while (path.size() != 0) {
  909.             pop();
  910.         }
  911.         split(offset + length, 0);
  912.         }
  913.         while (path.size() != 0) {
  914.         pop();
  915.         }
  916.     }
  917.  
  918.     boolean split(int offs, int len) {
  919.         boolean splitEnd = false;
  920.         // push the path
  921.         Element e = root;
  922.         int index = e.getElementIndex(offs);
  923.         while (! e.isLeaf()) {
  924.         push(e, index);
  925.         e = e.getElement(index);
  926.         index = e.getElementIndex(offs);
  927.         }
  928.  
  929.         ElemChanges ec = (ElemChanges) path.peek();
  930.         Element child = ec.parent.getElement(ec.index);
  931.         // make sure there is something to do... if the
  932.         // offset is already at a boundry then there is 
  933.         // nothing to do.
  934.         if (child.getStartOffset() != offs) {
  935.         // we need to split, now see if the other end is within
  936.         // the same parent.
  937.         int index0 = ec.index;
  938.         int index1 = index0;
  939.         if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
  940.             // it's a range split in the same parent
  941.             index1 = ec.parent.getElementIndex(offs+len);
  942.             if (index1 == index0) {
  943.             // it's a three-way split
  944.             ec.removed.addElement(child);
  945.             e = createLeafElement(ec.parent, child.getAttributes(),
  946.                           child.getStartOffset(), offs);
  947.             ec.added.addElement(e);
  948.             e = createLeafElement(ec.parent, child.getAttributes(),
  949.                       offs, offs + len);
  950.             ec.added.addElement(e);
  951.             e = createLeafElement(ec.parent, child.getAttributes(),
  952.                           offs + len, child.getEndOffset());
  953.             ec.added.addElement(e);
  954.             return true;
  955.             } else {
  956.             child = ec.parent.getElement(index1);
  957.             if ((offs + len) == child.getStartOffset()) {
  958.                 // end is already on a boundry
  959.                 index1 = index0;
  960.             }
  961.             }
  962.             splitEnd = true;
  963.         }
  964.  
  965.         // split the first location
  966.         pos = offs;
  967.         child = ec.parent.getElement(index0);
  968.         ec.removed.addElement(child);
  969.         e = createLeafElement(ec.parent, child.getAttributes(),
  970.                       child.getStartOffset(), pos);
  971.         ec.added.addElement(e);
  972.         e = createLeafElement(ec.parent, child.getAttributes(),
  973.                       pos, child.getEndOffset());
  974.         ec.added.addElement(e);
  975.  
  976.         // pick up things in the middle
  977.         for (int i = index0 + 1; i < index1; i++) {
  978.             child = ec.parent.getElement(i);
  979.             ec.removed.addElement(child);
  980.             ec.added.addElement(child);
  981.         }
  982.  
  983.         if (index1 != index0) {
  984.             child = ec.parent.getElement(index1);
  985.             pos = offs + len;
  986.             ec.removed.addElement(child);
  987.             e = createLeafElement(ec.parent, child.getAttributes(),
  988.                       child.getStartOffset(), pos);
  989.             ec.added.addElement(e);
  990.             e = createLeafElement(ec.parent, child.getAttributes(),
  991.                       pos, child.getEndOffset());
  992.             ec.added.addElement(e);
  993.         }
  994.         }
  995.         return splitEnd;
  996.     }
  997.  
  998.     /**
  999.      * Creates the UndoableEdit record for the edits made
  1000.      * in the buffer.
  1001.      */
  1002.     void endEdits(DefaultDocumentEvent de) {
  1003.         int n = changes.size();
  1004.         for (int i = 0; i < n; i++) {
  1005.         ElemChanges ec = (ElemChanges) changes.elementAt(i);
  1006.         Element[] removed = new Element[ec.removed.size()];
  1007.         ec.removed.copyInto(removed);
  1008.         Element[] added = new Element[ec.added.size()];
  1009.         ec.added.copyInto(added);
  1010.         int index = ec.index;
  1011.         ((BranchElement) ec.parent).replace(index, removed.length, added);
  1012.         ElementEdit ee = new ElementEdit((BranchElement) ec.parent, 
  1013.                          index, removed, added);
  1014.         de.addEdit(ee);
  1015.         }
  1016.         
  1017.         /*
  1018.         for (int i = 0; i < n; i++) {
  1019.         ElemChanges ec = (ElemChanges) changes.elementAt(i);
  1020.         System.err.print("edited: " + ec.parent + " at: " + ec.index +
  1021.             " removed " + ec.removed.size());
  1022.         if (ec.removed.size() > 0) {
  1023.             int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
  1024.             int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
  1025.             System.err.print("[" + r0 + "," + r1 + "]");
  1026.         }
  1027.         System.err.print(" added " + ec.added.size());
  1028.         if (ec.added.size() > 0) {
  1029.             int p0 = ((Element) ec.added.firstElement()).getStartOffset();
  1030.             int p1 = ((Element) ec.added.lastElement()).getEndOffset();
  1031.             System.err.print("[" + p0 + "," + p1 + "]");
  1032.         }
  1033.         System.err.println("");
  1034.         }
  1035.         */
  1036.     }
  1037.  
  1038.     /**
  1039.      * Initialize the buffer
  1040.      */
  1041.     void beginEdits(int offset, int length) {
  1042.         this.offset = offset;
  1043.         this.length = length;
  1044.         pos = offset;
  1045.         if (changes == null) {
  1046.         changes = new Vector();
  1047.         } else {
  1048.         changes.removeAllElements();
  1049.         }
  1050.         if (path == null) {
  1051.         path = new Stack();
  1052.         } else {
  1053.         path.removeAllElements();
  1054.         }
  1055.         if (endJoin == null) {
  1056.         endJoin = new Vector();
  1057.         } else {
  1058.         endJoin.removeAllElements();
  1059.         }
  1060.     }
  1061.  
  1062.     /**
  1063.      * Pushes a new element onto the stack that represents
  1064.      * the current path.
  1065.      * @param record Whether or not the push should be
  1066.      *  recorded as an element change or not.
  1067.      */
  1068.     void push(Element e, int index) {
  1069.         ElemChanges old = (ElemChanges) ((path.size() != 0) ? path.peek() : null);
  1070.         ElemChanges ec = new ElemChanges(e, index);
  1071.         path.push(ec);
  1072.     }
  1073.  
  1074.     void pop() {
  1075.         ElemChanges ec = (ElemChanges) path.peek();
  1076.         path.pop();
  1077.         if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
  1078.         changes.addElement(ec);
  1079.         } else if (! path.isEmpty()) {
  1080.         // if we pushed a branch element that didn't get
  1081.         // used, make sure its not marked as having been added.
  1082.         Element e = ec.parent;
  1083.         ec = (ElemChanges) path.peek();
  1084.         ec.added.removeElement(e);
  1085.         }
  1086.     }
  1087.  
  1088.     /**
  1089.      * move the current offset forward by n.
  1090.      */
  1091.     void advance(int n) {
  1092.         pos += n;
  1093.     }
  1094.  
  1095.     void insertElement(ElementSpec es) {
  1096.         ElemChanges ec = (ElemChanges) path.peek();
  1097.         switch(es.getType()) {
  1098.         case ElementSpec.StartTagType:
  1099.         Element belem = createBranchElement(ec.parent, es.getAttributes());
  1100.         ec.added.addElement(belem);
  1101.         push(belem, 0);
  1102.         break;
  1103.         case ElementSpec.EndTagType:
  1104.         pop();
  1105.         break;
  1106.         case ElementSpec.ContentType:
  1107.           int len = es.getLength();
  1108.         if (es.getDirection() != ElementSpec.JoinPreviousDirection) {
  1109.             Element leaf = createLeafElement(ec.parent, es.getAttributes(), 
  1110.                              pos, pos + len);
  1111.             ec.added.addElement(leaf);
  1112.         }
  1113.         pos += len;
  1114.         break;
  1115.         }
  1116.     }
  1117.         
  1118.     void removeElements(Element elem, int rmOffs0, int rmOffs1) {
  1119.         if (! elem.isLeaf()) {
  1120.         // update path for changes
  1121.         int index0 = elem.getElementIndex(rmOffs0);
  1122.         int index1 = elem.getElementIndex(rmOffs1);
  1123.         push(elem, index0);
  1124.  
  1125.         // if the range is contained by one element,
  1126.         // we just forward the request
  1127.         if (index0 == index1) {
  1128.             removeElements(elem.getElement(index0), rmOffs0, rmOffs1);
  1129.         } else {
  1130.             // the removal range spans elements.  If we can join
  1131.             // the two endpoints, do it.  Otherwise we remove the
  1132.             // interior and forward to the endpoints.
  1133.             Element child0 = elem.getElement(index0);
  1134.             Element child1 = elem.getElement(index1);
  1135.             ElemChanges ec = (ElemChanges) path.peek();
  1136.             if (canJoin(child0, child1)) {
  1137.             // remove and join
  1138.             for (int i = index0; i <= index1; i++) {
  1139.                 ec.removed.addElement(elem.getElement(i));
  1140.             }
  1141.             Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
  1142.             ec.added.addElement(e);
  1143.             } else {
  1144.             // remove interior and forward
  1145.             int rmIndex0 = index0 + 1;
  1146.             int rmIndex1 = index1 - 1;
  1147.             if (child0.getStartOffset() == rmOffs0) {
  1148.                 // start element completely consumed
  1149.                 child0 = null;
  1150.                 rmIndex0 = index0;
  1151.             }
  1152.             if (child1.getStartOffset() == rmOffs1) {
  1153.                 // end element not touched
  1154.                 child1 = null;
  1155.             }
  1156.             if (rmIndex0 <= rmIndex1) {
  1157.                 ec.index = rmIndex0;
  1158.             }
  1159.             for (int i = rmIndex0; i <= rmIndex1; i++) {
  1160.                 ec.removed.addElement(elem.getElement(i));
  1161.             }
  1162.             if (child0 != null) {
  1163.                 removeElements(child0, rmOffs0, rmOffs1);
  1164.             }
  1165.             if (child1 != null) {
  1166.                 removeElements(child1, rmOffs0, rmOffs1);
  1167.             }
  1168.             }
  1169.         }
  1170.  
  1171.         // publish changes
  1172.         pop();
  1173.         }
  1174.     }
  1175.  
  1176.     /**
  1177.      * Can the two given elements be coelesced together
  1178.      * into one element?
  1179.      */
  1180.     boolean canJoin(Element e0, Element e1) {
  1181.         if ((e0 == null) || (e1 == null)) {
  1182.         return false;
  1183.         }
  1184.         if (e0.getName().equals(ParagraphElementName) &&
  1185.         e1.getName().equals(ParagraphElementName)) {
  1186.         return true;
  1187.         }
  1188.         return e0.getAttributes().isEqual(e1.getAttributes());
  1189.     }
  1190.  
  1191.     /** 
  1192.      * Joins the two elements carving out a hole for the
  1193.      * given removed range.
  1194.      */
  1195.     Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) {
  1196.         if (left.isLeaf() && right.isLeaf()) {
  1197.         return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
  1198.                      right.getEndOffset());
  1199.         } else if ((!left.isLeaf()) && (!right.isLeaf())) {
  1200.         // join two branch elements.  This copies the children before
  1201.         // the removal range on the left element, and after the removal
  1202.         // range on the right element.  The two elements on the edge
  1203.         // are joined if possible and needed.
  1204.         Element to = createBranchElement(p, left.getAttributes());
  1205.         int ljIndex = left.getElementIndex(rmOffs0);
  1206.         int rjIndex = right.getElementIndex(rmOffs1);
  1207.         Element lj = left.getElement(ljIndex);
  1208.         if (lj.getStartOffset() == rmOffs0) {
  1209.             lj = null; 
  1210.         }
  1211.         Element rj = right.getElement(rjIndex);
  1212.         if (rj.getStartOffset() == rmOffs1) {
  1213.             rj = null;
  1214.         }
  1215.         Vector children = new Vector();
  1216.  
  1217.         // transfer the left
  1218.         for (int i = 0; i < ljIndex; i++) {
  1219.             children.addElement(clone(to, left.getElement(i)));
  1220.         }
  1221.  
  1222.         // transfer the join/middle
  1223.         if (canJoin(lj, rj)) {
  1224.             Element e = join(to, lj, rj, rmOffs0, rmOffs1);
  1225.             children.addElement(e);
  1226.         } else {
  1227.             if (lj != null) {
  1228.             children.addElement(clone(to, lj));
  1229.             }
  1230.             if (rj != null) {
  1231.             children.addElement(clone(to, rj));
  1232.             }
  1233.         }
  1234.  
  1235.         // transfer the right
  1236.         int n = right.getElementCount();
  1237.         for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
  1238.             children.addElement(clone(to, right.getElement(i)));
  1239.         }
  1240.  
  1241.         // install the children
  1242.         Element[] c = new Element[children.size()];
  1243.         children.copyInto(c);
  1244.         ((BranchElement)to).replace(0, 0, c);
  1245.         return to;
  1246.         } else {
  1247.         throw new StateInvariantError(
  1248.             "No support to join leaf element with non-leaf element");
  1249.         }
  1250.     }
  1251.  
  1252.     /**
  1253.      * Creates a copy of this element, with a different 
  1254.      * parent.
  1255.          *
  1256.          * @param parent the parent element
  1257.          * @param clonee the element to be cloned
  1258.          * @return the copy
  1259.      */
  1260.         public Element clone(Element parent, Element clonee) {
  1261.         if (clonee.isLeaf()) {
  1262.         return createLeafElement(parent, clonee.getAttributes(), 
  1263.                      clonee.getStartOffset(), 
  1264.                      clonee.getEndOffset());
  1265.         }
  1266.         Element e = createBranchElement(parent, clonee.getAttributes());
  1267.         int n = clonee.getElementCount();
  1268.         Element[] children = new Element[n];
  1269.         for (int i = 0; i < n; i++) {
  1270.         children[i] = clone(e, clonee.getElement(i));
  1271.         }
  1272.         ((BranchElement)e).replace(0, 0, children);
  1273.         return e;
  1274.     }
  1275.  
  1276.     /**
  1277.      * open the tree to fold in this data
  1278.      */
  1279.     void open(ElementSpec[] data) {
  1280.         int firstOffs = (data[0].getDirection() == ElementSpec.JoinPreviousDirection) ?
  1281.         offset + data[0].getLength() : offset;
  1282.         int lastOffs = offset + length;
  1283.         // FIXME - should actually join when end is a JoinNextDirection
  1284.  
  1285.         ElemChanges ec = (ElemChanges) path.peek();
  1286.         Element child = ec.parent.getElement(ec.index);
  1287.         boolean hasPop = false;
  1288.         for (int i = 0; i < data.length; i++) {
  1289.         if (data[i].getType() == ElementSpec.EndTagType) {
  1290.             hasPop = true;
  1291.             break;
  1292.         }
  1293.         }
  1294.         if (hasPop || (firstOffs != lastOffs)) {
  1295.         // split the entry point
  1296.         ec.removed.addElement(child);
  1297.         if (firstOffs != child.getStartOffset()) {
  1298.             // put the area before the insertion back into
  1299.             // the tree (ie the part before the split).
  1300.             Element shortened = 
  1301.             createLeafElement(ec.parent, child.getAttributes(),
  1302.                       child.getStartOffset(), firstOffs);
  1303.             ec.added.addElement(shortened);
  1304.         }
  1305.         if (child.getEndOffset() > lastOffs) {
  1306.             // pick up the content after the hole to be returned
  1307.             // back to the tree.
  1308.             int len = child.getEndOffset() - lastOffs;
  1309.             ElementSpec spec = 
  1310.             new ElementSpec(child.getAttributes(), 
  1311.                            ElementSpec.ContentType, len);
  1312.             endJoin.addElement(spec);
  1313.         }
  1314.         if (hasPop) {
  1315.             // pick up the remaining content to be placed into a new
  1316.             // parent.
  1317.             int n = ec.parent.getElementCount();
  1318.             for (int i = ec.index + 1; i < n; i++) {
  1319.             child = ec.parent.getElement(i);
  1320.             ec.removed.addElement(child);
  1321.             int len = child.getEndOffset() - child.getStartOffset();
  1322.             ElementSpec spec = 
  1323.                 new ElementSpec(child.getAttributes(), 
  1324.                            ElementSpec.ContentType, len);
  1325.             endJoin.addElement(spec);
  1326.             }
  1327.         }
  1328.         if (hasPop && (endJoin.size() == 0)) {
  1329.             // join into next paragraph, pull the next paragraph
  1330.             // from the tree.
  1331.             ec = (ElemChanges) path.elementAt(path.size() - 2);
  1332.             Element e = ec.parent.getElement(ec.index);
  1333.             if (e != null) {
  1334.             ec.removed.addElement(e);
  1335.             int n = e.getElementCount();
  1336.             for (int i = 0; i < n; i++) {
  1337.                 child = e.getElement(i);
  1338.                 int len = child.getEndOffset() - child.getStartOffset();
  1339.                 ElementSpec spec = 
  1340.                 new ElementSpec(child.getAttributes(), 
  1341.                         ElementSpec.ContentType, len);
  1342.                 endJoin.addElement(spec);
  1343.             }
  1344.             }
  1345.         }
  1346.         }
  1347.     }
  1348.  
  1349.     /**
  1350.      * finish mending the right side of the subtree
  1351.      * inserted into the element hierarchy.
  1352.      */
  1353.     void close() {
  1354.         ElemChanges ec = (ElemChanges) path.peek();
  1355.         int n = endJoin.size();
  1356.         for (int i = 0; i < n; i++) {
  1357.         ElementSpec spec = (ElementSpec) endJoin.elementAt(i);
  1358.         int p1 = pos + spec.getLength();
  1359.         Element e = createLeafElement(ec.parent, spec.getAttributes(), pos, p1);
  1360.         ec.added.addElement(e);
  1361.         pos = p1;
  1362.         }
  1363.     }
  1364.  
  1365.     Element root;
  1366.     transient int pos;          // current position
  1367.     transient int offset;
  1368.     transient int length;
  1369.     transient Vector endJoin;  // Vector<ElementSpec>
  1370.     transient Vector changes;  // Vector<ElemChanges>
  1371.     transient Stack path;      // Stack<ElemChanges>
  1372.     transient boolean insertOp;
  1373.  
  1374.     /*
  1375.      * Internal record used to hold element change specifications
  1376.      */
  1377.     class ElemChanges {
  1378.         
  1379.         ElemChanges(Element parent, int index) {
  1380.         this.parent = parent;
  1381.         this.index = index;
  1382.         added = new Vector();
  1383.         removed = new Vector();
  1384.         }
  1385.     
  1386.             public String toString() {
  1387.         return "added: " + added + "\nremoved: " + removed + "\n";
  1388.         }
  1389.         
  1390.         Element parent;
  1391.         int index;
  1392.         Vector added;
  1393.         Vector removed;
  1394.     }    
  1395.  
  1396.     }
  1397.  
  1398.     /**
  1399.      * An UndoableEdit used to remember AttributeSet changes to an
  1400.      * Element.
  1401.      */
  1402.     static class AttributeUndoableEdit extends AbstractUndoableEdit {
  1403.     AttributeUndoableEdit(Element element, AttributeSet newAttributes,
  1404.                   boolean isReplacing) {
  1405.         super();
  1406.         this.element = element;
  1407.         this.newAttributes = newAttributes;
  1408.         this.isReplacing = isReplacing;
  1409.         // If not replacing, it may be more efficient to only copy the
  1410.         // changed values...
  1411.         copy = element.getAttributes().copyAttributes();
  1412.     }
  1413.  
  1414.     /**
  1415.      * Redoes a change.
  1416.      *
  1417.      * @exception CannotRedoException if the change cannot be redone
  1418.      */
  1419.         public void redo() throws CannotRedoException {
  1420.         super.redo();
  1421.         MutableAttributeSet as = (MutableAttributeSet)element
  1422.                              .getAttributes();
  1423.         if(isReplacing)
  1424.         as.removeAttributes(as);
  1425.         as.addAttributes(newAttributes);
  1426.     }
  1427.  
  1428.     /**
  1429.      * Undoes a change.
  1430.      *
  1431.      * @exception CannotUndoException if the change cannot be undone
  1432.      */
  1433.         public void undo() throws CannotUndoException {
  1434.         super.undo();
  1435.         MutableAttributeSet as = (MutableAttributeSet)element.getAttributes();
  1436.         as.removeAttributes(newAttributes);
  1437.         as.addAttributes(copy);
  1438.     }
  1439.  
  1440.     // AttributeSet containing additional entries, must be non-mutable!
  1441.     protected AttributeSet newAttributes;
  1442.     // Copy of the AttributeSet the Element contained.
  1443.     protected AttributeSet copy;
  1444.     // true if all the attributes in the element were removed first.
  1445.     protected boolean isReplacing;
  1446.     // Efected Element.
  1447.     protected Element element;
  1448.     }
  1449.  
  1450.     /**
  1451.      * UndoableEdit for changing the resolve parent of an Element.
  1452.      */
  1453.     static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
  1454.     public StyleChangeUndoableEdit(AbstractElement element,
  1455.                        Style newStyle) {
  1456.         super();
  1457.         this.element = element;
  1458.         this.newStyle = newStyle;
  1459.         oldStyle = element.getResolveParent();
  1460.     }
  1461.  
  1462.     /**
  1463.      * Redoes a change.
  1464.      *
  1465.      * @exception CannotRedoException if the change cannot be redone
  1466.      */
  1467.         public void redo() throws CannotRedoException {
  1468.         super.redo();
  1469.         element.setResolveParent(newStyle);
  1470.     }
  1471.  
  1472.     /**
  1473.      * Undoes a change.
  1474.      *
  1475.      * @exception CannotUndoException if the change cannot be undone
  1476.      */
  1477.         public void undo() throws CannotUndoException {
  1478.         super.undo();
  1479.         element.setResolveParent(oldStyle);
  1480.     }
  1481.  
  1482.     /** Element to change resolve parent of. */
  1483.     protected AbstractElement element;
  1484.     /** New style. */
  1485.     protected Style newStyle;
  1486.     /** Old style, before setting newStyle. */
  1487.     protected AttributeSet oldStyle;
  1488.     }
  1489. }
  1490.  
  1491.  
  1492.  
  1493.  
  1494.